Conversation
Added getSubscriptionContextRequest() in the JS bridge; injected Promise-based getSubscriptionContext in both HTML-block and fullscreen banner scripts in AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter.
cleverpush/src/main/java/com/cleverpush/banner/AppBannerCarouselAdapter.java
Show resolved
Hide resolved
cleverpush/src/main/java/com/cleverpush/banner/AppBannerCarouselAdapter.java
Outdated
Show resolved
Hide resolved
Removed unused import
cleverpush/src/main/java/com/cleverpush/banner/AppBannerCarouselAdapter.java
Show resolved
Hide resolved
| ); | ||
| String contextJson = getSubscriptionContextJson(); | ||
| webView.evaluateJavascript( | ||
| "if (typeof CleverPush !== 'undefined') { CleverPush.subscriptionContext = " + contextJson + "; }", |
There was a problem hiding this comment.
XSS vulnerability from unescaped JSON in JavaScript
High Severity
The raw JSON from getSubscriptionContextJson() is directly concatenated into JavaScript code without proper escaping. If subscription IDs or channel IDs contain characters like </script>, <, >, or /, they could break out of the JavaScript context and enable XSS attacks. While Gson.toJson() handles basic JSON escaping, it doesn't prevent script injection when the JSON is embedded directly in JavaScript code.
Additional Locations (2)
| if (webView == null) return; | ||
| String contextJson = getSubscriptionContextJson(); | ||
| String escaped = contextJson.replace("\\", "\\\\").replace("\"", "\\\"") | ||
| .replace("\r", "\\r").replace("\n", "\\n"); |
There was a problem hiding this comment.
Incomplete character escaping for JavaScript injection
Medium Severity
The manual escaping only handles backslash, quote, carriage return, and newline characters, but omits tab (\t) and other control characters. If the JSON contains unescaped tabs or control characters, they could cause JavaScript syntax errors when the string is parsed by JSON.parse(), breaking the Promise resolution and preventing subscription context from being delivered.
Additional Locations (1)
cleverpush/src/main/java/com/cleverpush/banner/AppBannerCarouselAdapter.java
Outdated
Show resolved
Hide resolved
|
|
||
| public CleverpushInterface(WebView webView) { | ||
| this.webView = webView; | ||
| } |
There was a problem hiding this comment.
Memory leak from circular WebView reference
Medium Severity
The CleverpushInterface stores a reference to the WebView that it's added to via addJavascriptInterface. This creates a circular reference: WebView holds the interface, and the interface holds the WebView. This prevents garbage collection and causes memory leaks when WebViews are destroyed, particularly in RecyclerView scenarios where views are recycled.
Additional Locations (1)
| }); | ||
|
|
||
| webView.loadUrl(block.getUrl()); | ||
| webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush"); |
There was a problem hiding this comment.
Promise never rejects leading to unhandled hangs
Medium Severity
The CleverPush.getSubscriptionContext Promise only accepts a resolve callback, never a reject. If getSubscriptionContextRequest returns early due to null webView (line 996) or if any other error prevents resolution, the Promise hangs indefinitely. JavaScript callers using await or .then() will never receive a response, blocking execution.
Additional Locations (1)
|
|
||
| webView.loadUrl(block.getUrl()); | ||
| webView.addJavascriptInterface(new CleverpushInterface(), "CleverPush"); | ||
| webView.addJavascriptInterface(new CleverpushInterface(webView), "CleverPush"); |
There was a problem hiding this comment.
JavaScript interface added after URL load
High Severity
The addJavascriptInterface is called after loadUrl, but according to Android WebView documentation, adding an interface is not reflected on the JavaScript side until the next page load. This means the CleverPush object won't be available when the page loads or in onPageFinished, causing JavaScript errors when code tries to call CleverPush.getSubscriptionContextRequest() and other interface methods.
Additional Locations (1)
| " return new Promise(function(resolve) {\n" + | ||
| " window.CleverPush._resolveSubscriptionContext = resolve;\n" + | ||
| " CleverPush.getSubscriptionContextRequest();\n" + | ||
| " });\n" + |
There was a problem hiding this comment.
Concurrent Promise calls overwrite resolver
Medium Severity
If JavaScript calls CleverPush.getSubscriptionContext() multiple times concurrently, the second call overwrites window.CleverPush._resolveSubscriptionContext, causing the first Promise to never resolve. Only the most recent call's Promise will resolve; all previous Promises hang indefinitely, leaking memory and blocking any code waiting on those Promises.
Additional Locations (1)
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with Cloud Agents, enable Autofix in the Cursor dashboard.
| Map<String, Object> empty = new LinkedHashMap<>(); | ||
| empty.put("subscriptionId", (Object) null); | ||
| empty.put("channelId", (Object) null); | ||
| return new Gson().toJson(empty); |
There was a problem hiding this comment.
Gson silently drops null values from subscription context JSON
High Severity
new Gson().toJson(context) silently omits null map entries because Gson's default is serializeNulls = false. When getSubscriptionId or getChannelId returns null, the output becomes {} instead of the intended {"subscriptionId":null,"channelId":null}. The catch block is equally broken — it explicitly puts null values, but Gson strips them. This contradicts the stated goal of matching iOS behavior with null for missing values. Using new GsonBuilder().serializeNulls().create() instead of new Gson() would fix this.


Added getSubscriptionContextRequest() in the JS bridge; injected Promise-based getSubscriptionContext in both HTML-block and fullscreen banner scripts in AppBannerCarouselAdapter and InboxDetailBannerCarouselAdapter.
Note
Medium Risk
Touches WebView JS injection and
@JavascriptInterfacesurfaces; mistakes in JSON escaping or bridge wiring could break HTML banners or expose unexpected JS-bridge behavior.Overview
Adds a new JS-bridge API for banner WebViews to retrieve the current subscription context (
subscriptionId,channelId). BothAppBannerCarouselAdapterandInboxDetailBannerCarouselAdapternow injectCleverPush.subscriptionContexton load and provide a Promise-basedCleverPush.getSubscriptionContext()that is resolved via a new@JavascriptInterfacemethodgetSubscriptionContextRequest().To support async callbacks,
CleverpushInterfacenow receives the owningWebView, and subscription context JSON generation is centralized ingetSubscriptionContextJson()(null-safe, iOS-compatible).Written by Cursor Bugbot for commit a75daae. This will update automatically on new commits. Configure here.
Summary by cubic
Adds a JS bridge to send subscription context (subscriptionId, channelId) to banner WebViews and exposes a Promise-based CleverPush.getSubscriptionContext, with subscriptionContext preloaded. Fulfills CP-10963 and mirrors iOS behavior.
New Features
Refactors
Written for commit a75daae. Summary will update on new commits.